import Phaser from "phaser";
import QGameFunction from "./QGameFunction";
import { SpineGameObject, TrackEntry } from "@esotericsoftware/spine-phaser";

// Extend Phaser's Sprite and SpineGameObject to allow QRPGSpriteObject property
declare module "phaser" {
    namespace Physics.Matter {
        interface Sprite {
            QRPGSpriteObject?: any;
        }
    }
}
declare global {
    interface SpineGameObject {
        QRPGSpriteObject?: any;
    }
}


/**
 * @class QSpriteObject
 * @description 精灵基础组件
 */
export default class QGameSpriteObject {
    public type = "QSpriteObject";
    public uuid: string = QGameFunction.generateUUID();
    public parent: QGameSpriteObject | null = null;

    public name: string = "QSpriteObject";
    public scene: Phaser.Scene;
    public sprite: Phaser.Physics.Matter.Sprite | null = null;
    public spine: SpineGameObject | null = null;
    public colliderBody: MatterJS.BodyType | null = null;

    public angleOffset: number = 0;

    /** 精灵是否为本地精灵, 理论上只有一个本地玩家 */
    public isLocal: boolean = true;

    public onCollision: Function | null = null;
    public fieldViewMapGrid: number[][] | undefined;
    public lastFieldViewMapGridTime: number = 0;

    public groups: number = 0;

    constructor(
        scene: Phaser.Scene,
        parent: QGameSpriteObject | null = null,
        isLocal: boolean = true
    ) {
        this.scene = scene;
        this.parent = parent;
        this.isLocal = isLocal;
    }

    public initSprite(
        x: number,
        y: number,
        key: string,
        frame?: any
    ): Phaser.Physics.Matter.Sprite {
        this.sprite = <Phaser.Physics.Matter.Sprite>(
            this.scene.matter.add.sprite(x, y, key, frame)
        );
        /** 添加物理碰撞体 */
        this.sprite["QRPGSpriteObject"] = this;
        return this.sprite;
    }

    public initSpine(
        x: number,
        y: number,
        key: string,
        skin: string | null = null
    ) {
        this.spine = this.scene.add.spine(x, y, key + "-data", key + "-atlas");
        if (skin != null) {
            this.spine.skeleton.setSkinByName(skin);
            (this.spine as any)["QRPGSpriteObject"] = this;
        }

        return this.spine;
    }

    public getX() {
        if (this.spine) return this.spine.x;
        if (this.sprite) return this.sprite.x;
        return 0;
    }
    public getY() {
        if (this.spine) return this.spine.y;
        if (this.sprite) return this.sprite.y;
        return 0;
    }

    public setX(x: number) {
        if (this.spine) this.spine.x = x;
        if (this.sprite) this.sprite.x = x;
    }
    public setY(y: number) {
        if (this.spine) this.spine.y = y;
        if (this.sprite) this.sprite.y = y;
    }

    public setAngle(angle: number) {
        if (this.spine) this.spine.angle = angle + this.angleOffset;
        if (this.sprite) this.sprite.angle = angle + this.angleOffset;
    }

    public getAngle() {
        if (this.spine) return this.spine.angle;
        if (this.sprite) return this.sprite.angle;
        return 0;
    }

    public setRotation(rotation: number) {
        if (this.spine) this.spine.rotation = rotation;
        if (this.sprite) this.sprite.rotation = rotation;
    }
    public getRotation() {
        if (this.spine) return this.spine.rotation;
        if (this.sprite) return this.sprite.rotation;
        return 0;
    }

    public getDepth() {
        if (this.spine) return this.spine.depth;
        if (this.sprite) return this.sprite.depth;
        return 0;
    }

    public setDepth(depth: number) {
        if (this.spine) this.spine.setDepth(depth);
        if (this.sprite) this.sprite.setDepth(depth);
    }
    public calcDistance(sprite: QGameSpriteObject) {
        if (this.spine === null && this.sprite === null) return 0;
        if (sprite.spine === null && sprite.sprite === null) return 0;
        const dx = this.getX() - sprite.getX();
        const dy = this.getY() - sprite.getY();
        const distance = Math.sqrt(dx * dx + dy * dy);
        return distance;
    }

    public calcHeuristic(sprite: QGameSpriteObject) {
        if (this.spine === null && this.sprite === null) return 0;
        if (sprite.spine === null && sprite.sprite === null) return 0;
        const dx = Math.abs(this.getX() - sprite.getX());
        const dy = Math.abs(this.getY() - sprite.getY());
        return dx + dy;
    }

    public setVelocity(x: number, y: number): void {
        if (this.sprite) {
            this.sprite.setVelocity(x, y);
        }
        if (this.spine && typeof (this.spine as any)["setVelocity"] === "function") {
            (this.spine as any)["setVelocity"](x, y);
        }
    }
    public setVelocityX(x: number): void {
        if (this.sprite) {
            this.sprite.setVelocityX(x);
        }
        if (this.spine && typeof (this.spine as any)["setVelocityX"] === "function") {
            (this.spine as any)["setVelocityX"](x);
        }
    }
    public setVelocityY(y: number): void {
        if (this.sprite) {
            this.sprite.setVelocityY(y);
        }
        if (this.spine && typeof (this.spine as any)["setVelocityY"] === "function") {
            (this.spine as any)["setVelocityY"](y);
        }
    }

    public addCircleCollider(radius: number, isStatic: boolean = false): void {
        if (this.sprite) {
            this.sprite["QRPGSpriteObject"] = this;
            this.colliderBody = this.scene.matter.add.circle(
                this.sprite.x,
                this.sprite.y,
                radius,
                {
                    isStatic: isStatic,
                    plugin: { QSprite: this },
                    onCollideCallback: (pair: MatterJS.IPair) => {
                        const bodyAPlugin = (pair.bodyA as any).plugin;
                        const bodyBPlugin = (pair.bodyB as any).plugin;
                        if (this != bodyBPlugin?.QSprite) {
                            this.collision(
                                bodyBPlugin?.QSprite,
                                pair
                            );
                        } else {
                            this.collision(
                                bodyAPlugin?.QSprite,
                                pair
                            );
                        }
                    },
                }
            );
            this.sprite.setExistingBody(this.colliderBody);
        } else if (this.spine) {
            (this.spine as any)["QRPGSpriteObject"] = this;
            this.colliderBody = this.scene.matter.add.circle(
                this.spine.x,
                this.spine.y,
                radius,
                {
                    isStatic: isStatic,
                    plugin: { QSprite: this },
                    onCollideCallback: (pair: MatterJS.IPair) => {
                        const bodyBPlugin = (pair.bodyB as any).plugin;
                        const bodyAPlugin = (pair.bodyA as any).plugin;
                        if (this != bodyBPlugin?.QSprite) {
                            this.collision(
                                bodyBPlugin?.QSprite,
                                pair
                            );
                        } else {
                            this.collision(
                                bodyAPlugin?.QSprite,
                                pair
                            );
                        }
                    },
                }
            );
            this.scene.matter.add.gameObject(this.spine, this.colliderBody);
        }
    }

    public addRectangleCollider(
        width: number,
        height: number,
        x: number,
        y: number,
        isStatic: boolean = false
    ): void {
        if (this.sprite) {
            this.colliderBody = this.scene.matter.add.rectangle(
                x,
                y,
                width,
                height,
                {
                    isStatic: isStatic,
                    plugin: { QSprite: this },
                    onCollideCallback: (pair: MatterJS.IPair) => {
                        const bodyBPlugin = (pair.bodyB as any).plugin;
                        const bodyAPlugin = (pair.bodyA as any).plugin;
                        if (this != bodyBPlugin?.QSprite) {
                            this.collision(
                                bodyBPlugin?.QSprite,
                                pair
                            );
                        } else {
                            this.collision(
                                bodyAPlugin?.QSprite,
                                pair
                            );
                        }
                    },
                }
            );
            this.sprite.setExistingBody(this.colliderBody);
        } else if (this.spine) {
            this.colliderBody = this.scene.matter.add.rectangle(
                x,
                y,
                width,
                height,
                {
                    isStatic: isStatic,
                    plugin: { QSprite: this },
                    onCollideCallback: (pair: MatterJS.IPair) => {
                        const bodyBPlugin = (pair.bodyB as any).plugin;
                        const bodyAPlugin = (pair.bodyA as any).plugin;
                        if (this != bodyBPlugin?.QSprite) {
                            this.collision(
                                bodyBPlugin?.QSprite,
                                pair
                            );
                        } else {
                            this.collision(
                                bodyAPlugin?.QSprite,
                                pair
                            );
                        }
                    },
                }
            );
            this.scene.matter.add.gameObject(this.spine, this.colliderBody);
        }
    }

    /** 触发碰撞后被调用 */
    public collision(sprite: any, pair?: any): void {
        if (this.onCollision) {
            console.log(this, sprite);
            this.onCollision(this, sprite, pair);
        }
    }

    public hide(): void {
        if (this.sprite) {
            this.sprite.setVisible(false);
        }
        if (this.spine) {
            this.spine.setVisible(false);
        }
        if (this.colliderBody) {
            this.colliderBody.collisionFilter.mask = 0;
        }
    }

    public show(): void {
        if (this.sprite) {
            this.sprite.setVisible(true);
        }
        if (this.spine) {
            this.spine.setVisible(true);
        }
        if (this.colliderBody) {
            this.colliderBody.collisionFilter.mask = 1;
        }
    }

    /**
     * 播放指定动画
     * @param key 动画名称
     * @param loop 是否循环播放
     * @param callback 动画播放完成回调函数
     */
    public play(
        key: string,
        loop: boolean = true,
        callback?: (track?: TrackEntry) => void
    ): void {
        if (this.sprite) {
            this.sprite.play({
                key: key,
                repeat: loop ? -1 : 0,
                delay: 0,
                repeatDelay: 0,
            });
        }
        if (this.spine) {
            const track: TrackEntry = this.spine.animationState.setAnimation(
                0,
                key,
                loop
            );
            // 监听动画完成事件
            track.listener = {
                complete: (entry) => {
                    if (callback) {
                        callback(entry);
                    }
                },
            };
        }
    }

    public stopPlay() {
        if (this.sprite) {
            this.sprite.stop();
        }
        if (this.spine) {
            this.spine.animationState.clearTrack(0);
        }
    }

    /**
     * 设置摄像机跟随精灵对象
     */
    public setCameraFollow() {
        // this.scene.cameras.main.width = 960
        // this.scene.cameras.main.height = 540
        if (this.sprite) {
            this.scene.cameras.main.startFollow(this.sprite, true, 1, 1, 0, 0);
            return;
        }
        if (this.spine) {
            this.scene.cameras.main.startFollow(this.spine, true, 1, 1, 0, 0);
            return;
        }
    }

    public destroy() {
        if (this.sprite) {
            this.sprite.destroy();
        }
        if (this.spine) {
            this.spine.destroy();
        }
        if (this.colliderBody) {
            this.scene.matter.world.remove(this.colliderBody);
        }
    }

    /**
     * 玩家输入信息更新
     * 单机游戏中，改方法由Phaser.Scene.update()调用，在多人游戏中，该方法由客户端调用。
     */
    public update(time: number, delta: number): void {}
}
